package com.kawins.supers.upload.fastdfs.pool;

import org.apache.commons.lang3.StringUtils;
import org.csource.common.MyException;
import org.csource.fastdfs.ClientGlobal;
import org.csource.fastdfs.ProtoCommon;
import org.csource.fastdfs.TrackerClient;
import org.csource.fastdfs.TrackerServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Properties;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * @author rankai
 *         createTime 2017-03-2017/3/6 14:15
 */
public class ConnectionPool {

    private static final Logger log = LoggerFactory.getLogger(ConnectionPool.class);

    private static final int DEF_CONNECT_TIMEOUT_IN_SECONDS = 2;
    private static final int DEF_NETWORK_TIMEOUT_IN_SECONDS = 30;
    private static final String DEF_CHARSET = "utf-8";
    private static final String DEF_HTTP_ANTI_STEAL_TOKEN = "no";
    private static final String DEF_HTTP_SECRET_KEY = "FastDFS1234567890";
    private static final int DEF_HTTP_TRACKER_HTTP_PORT = 80;
    private static final int DEF_TRY_COUNT = 3;

    private static final int DEF_MIN_POOL_SIZE = 1;
    private static final int DEF_MAX_POOL_SIZE = 20;
    private static final int DEF_WAIT_TIMES = 1;

    private int connectTimeoutInSeconds = DEF_CONNECT_TIMEOUT_IN_SECONDS;
    private int networkTimeoutInSeconds = DEF_NETWORK_TIMEOUT_IN_SECONDS;
    private String charset = DEF_CHARSET;
    private String httpAntiStealToken = DEF_HTTP_ANTI_STEAL_TOKEN;
    private String httpSecretKey = DEF_HTTP_SECRET_KEY;
    private int httpTrackerHttpPort = DEF_HTTP_TRACKER_HTTP_PORT;
    private String trackerServers;

    private int minPoolSize = DEF_MIN_POOL_SIZE; //连接池默认最小连接数
    private int maxPoolSize = DEF_MAX_POOL_SIZE; //连接池默认最大连接数
    private int waitTimes = DEF_WAIT_TIMES; //默认等待时间（单位：秒）
    private int tryCount = DEF_TRY_COUNT; //fastdfs客户端创建连接默认1次


    private LinkedBlockingQueue<TrackerServer> idleConnectionPool; //空闲的连接池
    private volatile int nowPoolSize = 0; //当前创建的连接数
    private static Properties properties = new Properties();

    private HeartBeat beat;

    public void init() throws MyException {
        if (StringUtils.isBlank(trackerServers)) {
            throw new MyException("trackerServer参数为空");
        } else {
            properties.put("fastdfs.connect_timeout_in_seconds", connectTimeoutInSeconds);
            properties.put("fastdfs.network_timeout_in_seconds", networkTimeoutInSeconds);
            properties.put("fastdfs.charset", charset);
            properties.put("fastdfs.http_anti_steal_token", httpAntiStealToken);
            properties.put("fastdfs.http_secret_key", httpSecretKey);
            properties.put("fastdfs.http_tracker_http_port", httpTrackerHttpPort);
            properties.put("fastdfs.tracker_servers", trackerServers);
            poolInit();
        }
    }


    /**
     * @Description: 连接池初始化 (在加载当前ConnectionPool时执行) 1).加载配置文件 2).空闲连接池初始化；
     * 3).创建最小连接数的连接，并放入到空闲连接池；
     */
    private void poolInit() {
        try {
            initClientGlobal();
            idleConnectionPool = new LinkedBlockingQueue<>();
            for (int i = 0; i < minPoolSize; i++) {
                createTrackerServer();
            }
            if (minPoolSize > idleConnectionPool.size()) {
                log.error("初始化fastdfsPool数量异常", "实际pool数量为:[" + idleConnectionPool.size() + "],应为[" + minPoolSize + "]");
            }
        } catch (Exception e) {
            log.error("initClientGlobal执行失败");
            e.printStackTrace();
        }
    }

    /**
     * @Description: 创建TrackerServer, 并放入空闲连接池
     */
    private void createTrackerServer() {
        TrackerServer trackerServer = null;
        TrackerClient trackerClient = new TrackerClient();
        try {
            trackerServer = trackerClient.getConnection();
            int i = 1;
            while (trackerServer == null && tryCount != 0) {
                log.warn("获取trackerServer失败,正在进行第[" + i++ + "]次尝试");
                tryCount--;
                initClientGlobal();
                trackerServer = trackerClient.getConnection();
            }
            if (trackerServer != null) {
                if (ProtoCommon.activeTest(trackerServer.getSocket())) {
                    idleConnectionPool.add(trackerServer);
                    synchronized (this) {
                        nowPoolSize++;
                    }
                }
            }
        } catch (IOException e) {
            log.error("getConnection()失败");
            e.printStackTrace();
        } catch (Exception e) {
            log.error("initClientGlobal执行失败");
            e.printStackTrace();
        } finally {
            if (trackerServer != null) {
                try {
                    trackerServer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }

    /**
     * @throws Exception
     * @Description: 获取空闲连接 1).在空闲池（idleConnectionPool)中弹出一个连接；
     * 2).把该连接放入忙碌池（busyConnectionPool）中; 3).返回 connection
     * 4).如果没有idle connection, 等待 wait_time秒, and check again
     */

    public TrackerServer checkout() {
        TrackerServer trackerServer = idleConnectionPool.poll();
        if (trackerServer == null) {
            if (nowPoolSize < maxPoolSize) {
                createTrackerServer();
                try {
                    trackerServer = idleConnectionPool.poll(waitTimes, TimeUnit.SECONDS);
                } catch (Exception e) {
                    log.error("连接池重启", "原因" + e.getMessage());
                }
            }
        }
        return trackerServer;
    }

    /**
     * @Description: 释放繁忙连接 1.如果空闲池的连接小于最小连接值，就把当前连接放入idleConnectionPool；
     * 2.如果空闲池的连接等于或大于最小连接值，就把当前释放连接丢弃；
     * <p>
     * 需释放的连接对象
     */

    public void checkin(TrackerServer trackerServer) {
        if (trackerServer != null) {
            if (idleConnectionPool.size() < minPoolSize) {
                idleConnectionPool.add(trackerServer);
            } else {
                synchronized (this) {
                    if (nowPoolSize != 0) {
                        nowPoolSize--;
                    }
                }
            }
        }
    }

    /**
     * @param trackerServer
     * @Description: 删除不可用的连接，并把当前连接数减一（调用过程中trackerServer报异常，调用一般在finally中）
     */
    public void drop(TrackerServer trackerServer) {
        if (trackerServer != null) {
            try {
                synchronized (this) {
                    if (nowPoolSize != 0) {
                        nowPoolSize--;
                    }
                }
                trackerServer.close();
            } catch (IOException e) {
                log.error("连接池删除无用连接", "原因" + e.getMessage());
            }
        }
    }

    private void initClientGlobal() throws Exception {
        ClientGlobal.initByProperties(properties);
    }

    public LinkedBlockingQueue<TrackerServer> getIdleConnectionPool() {
        return idleConnectionPool;
    }

    public void setConnectTimeoutInSeconds(int connectTimeoutInSeconds) {
        this.connectTimeoutInSeconds = connectTimeoutInSeconds;
    }

    public void setNetworkTimeoutInSeconds(int networkTimeoutInSeconds) {
        this.networkTimeoutInSeconds = networkTimeoutInSeconds;
    }

    public void setCharset(String charset) {
        this.charset = charset;
    }

    public void setHttpAntiStealToken(String httpAntiStealToken) {
        this.httpAntiStealToken = httpAntiStealToken;
    }

    public void setHttpSecretKey(String httpSecretKey) {
        this.httpSecretKey = httpSecretKey;
    }

    public void setHttpTrackerHttpPort(int httpTrackerHttpPort) {
        this.httpTrackerHttpPort = httpTrackerHttpPort;
    }

    public void setTrackerServers(String trackerServers) {
        this.trackerServers = trackerServers;
    }

    public void setMinPoolSize(int minPoolSize) {
        this.minPoolSize = minPoolSize;
    }

    public void setMaxPoolSize(int maxPoolSize) {
        this.maxPoolSize = maxPoolSize;
    }

    public void setWaitTimes(int waitTimes) {
        this.waitTimes = waitTimes;
    }

    public void setTryCount(int tryCount) {
        this.tryCount = tryCount;
    }
}


